// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/digsig.h>
#include <linux/uaccess.h>
#include <crypto/public_key.h>
#include <keys/system_keyring.h>
#include <linux/key-type.h>
#include <linux/key.h>
#include <linux/err.h>
#include <linux/cred.h>
#include <linux/vmalloc.h>
#include <crypto/hash.h>
#include <linux/fs.h>
#include <linux/kernel_read_file.h>

#ifdef CONFIG_EULEROS_SIGNATURE_PSS
#include <linux/crl_revocate.h>
#endif

#include "dim_core.h"
#include "../common/dim_hash.h"

static struct key *dim_keyring;

/* currently use sha256 algo to cal digest */
#ifndef CONFIG_EULEROS_SIGNATURE_PSS
static enum dim_hash_type sig_hash_index = DIM_HASH_SHA256;
static struct key *dim_key;
#endif

int dim_sig_verify(const char *buf,
		   loff_t buf_len,
		   const char *sbuf,
		   loff_t sbuf_len)
{
#ifdef CONFIG_EULEROS_SIGNATURE_PSS
	return mod_verify_cms_data(buf, buf_len, sbuf, sbuf_len);
#else
	int ret;
	char digest_out[MAX_DIGEST_SIZE];
	char *sig_data = NULL;
	struct crypto_shash *tfm = dim_hash_get_tfm(sig_hash_index);
	struct public_key_signature key_sig = {
		.pkey_algo = "rsa",
		.hash_algo = "sha256",
		.encoding = "pkcs1"
	};

	DIM_SHASH_DESC_ON_STACK(shash);

	if (buf == NULL || sbuf == NULL)
		return -EINVAL;

	if (tfm == NULL)
		return -EOPNOTSUPP;

	shash->tfm = tfm;

	ret = crypto_shash_init(shash);
	if (ret < 0) {
		pr_warn("shash init failed: %d\n", ret);
		return ret;
	}

	crypto_shash_update(shash, (char *)buf, buf_len);
	crypto_shash_final(shash, digest_out);

	sig_data = kmemdup(sbuf, sbuf_len, GFP_KERNEL);
	if (sig_data == NULL)
		return -ENOMEM;

	key_sig.s = sig_data;
	key_sig.s_size = sbuf_len;
	key_sig.digest = digest_out;
	key_sig.digest_size = dim_hash_get_digest_len(sig_hash_index);

	ret = verify_signature(dim_key, &key_sig);
	kfree(sig_data);
	return ret;
#endif
}

int dim_sig_init(void)
{
#ifdef CONFIG_EULEROS_SIGNATURE_PSS
	return 0;
#else
	int ret;
	void *data = NULL;
	loff_t size;
	key_ref_t key;
	struct key_restriction *restriction = NULL;

	if (dim_keyring != NULL)
		return 0;

	restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL);
	if (restriction == NULL)
		return -ENOMEM;

	restriction->check = restrict_link_by_builtin_trusted;

	dim_keyring = keyring_alloc("_dim", KUIDT_INIT(0),
				    KGIDT_INIT(0), current_cred(),
				    ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
				    KEY_USR_VIEW | KEY_USR_READ |
				    KEY_USR_WRITE | KEY_USR_SEARCH),
				    KEY_ALLOC_NOT_IN_QUOTA,
				    restriction, NULL);
	if (IS_ERR(dim_keyring)) {
		ret = PTR_ERR(dim_keyring);
		pr_info("fail to allocate keyring: %d\n", ret);
		dim_keyring = NULL;
		kfree(restriction);
		return ret;
	}

	ret = kernel_read_file_from_path(CONFIG_EULEROS_DIM_X509_PATH, 0, &data,
		DIM_MAX_CERT_FILE_SIZE, NULL, READING_X509_CERTIFICATE);
	if (ret < 0) {
		pr_err("fail to open cert file %s: %d\n",
			CONFIG_EULEROS_DIM_X509_PATH, ret);
		goto out;
	}

	size = ret;

	key = key_create_or_update(make_key_ref(dim_keyring, 1),
				   "asymmetric", NULL,
				   data, size,
				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
				   KEY_USR_VIEW | KEY_USR_READ),
				   KEY_ALLOC_NOT_IN_QUOTA);
	if (IS_ERR(key)) {
		ret = PTR_ERR(key);
		pr_err("fail to load X.509 cert file %s: %d\n",
			CONFIG_EULEROS_DIM_X509_PATH, ret);
		goto out;
	}

	pr_info("loaded X.509 cert ok\n");
	key_ref_put(key);
	dim_key = key_ref_to_ptr(key);

	ret = 0;
out:
	if (data != NULL)
		vfree(data);
	if (ret) {
		key_put(dim_keyring);
		dim_keyring = NULL;
		dim_key = NULL;
	}
	return ret;
#endif
}

void dim_sig_uninit(void)
{
	if (dim_keyring != NULL) {
		key_put(dim_keyring);
		dim_keyring = NULL;
	}
}
